/***********************************************************************
	filename: 	CEGUISystem.cpp
	created:	20/2/2004
	author:		Paul D Turner

	purpose:	Implementation of main system object
*************************************************************************/
#include "hgeGuiSystem.h"
#include "hgeLogger.h"
#include "hgeGuiWindowFactoryManager.h"
#include "hgeGuiWindowFactory.h"
#include "hgeGuiWindowManager.h"
#include "hgeMouseCursor.h"
#include "hgeGuiWindow.h"
#include "hgeImageManager.h"
#include "hgeExceptions.h"
#include "hgePropertyHelper.h"
#include "hgeGuiWindowRendererManager.h"
#include "hgeRenderingSurface.h"
#include "hgeMouseCursor.h"
#include "hgeRenderingContext.h"
#include "hgeFontManager.h"
#include "hgePathMgr.h"
#include "elements/hgeGuiAll.h"
#include <ctime>
#include <clocale>

#define S_(X) #X
#define STRINGIZE(X) S_(X)

const String System::EventNamespace("System");

/*!
\brief
    Simple timer class.
*/
class SimpleTimer
{
    double d_baseTime;

public:
    static double currentTime(); ///< returns time in seconds

    SimpleTimer() : d_baseTime(currentTime()) {}

    void restart() { d_baseTime = currentTime(); }
    double elapsed() { return currentTime() - d_baseTime; }
};

#if defined(__WIN32__) || defined(_WIN32)
#include <windows.h>
double SimpleTimer::currentTime()
{
    return timeGetTime() / 1000.0;
}
#endif


/*!
\brief
	Implementation structure used in tracking up & down mouse button inputs in order to generate click, double-click,
	and triple-click events.
*/
struct MouseClickTracker
{
	MouseClickTracker(void) : d_click_count(0) {}

	SimpleTimer		d_timer;			//!< Timer used to track clicks for this button.
	int				d_click_count;		//!< count of clicks made so far.
	hgeRect			d_click_area;		//!< area used to detect multi-clicks
    Window*         d_target_window;    //!< target window for any events generated.
};


struct MouseClickTrackerImpl
{
	MouseClickTracker	click_trackers[MouseButtonCount];
};


/*************************************************************************
	Static Data Definitions
*************************************************************************/
// singleton instance pointer
template<> System* Singleton<System>::ms_Singleton	= 0;

// click event generation defaults
const double	System::DefaultSingleClickTimeout	= 0.0; // was 0.2
const double	System::DefaultMultiClickTimeout	= 0.33;
const Size		System::DefaultMultiClickAreaSize	= Size(12,12);

// event names
const String System::EventGUISheetChanged( "GUISheetChanged" );
const String System::EventSingleClickTimeoutChanged( "SingleClickTimeoutChanged" );
const String System::EventMultiClickTimeoutChanged( "MultiClickTimeoutChanged" );
const String System::EventMultiClickAreaSizeChanged( "MultiClickAreaSizeChanged" );
const String System::EventDefaultFontChanged( "DefaultFontChanged" );
const String System::EventDefaultMouseCursorChanged( "DefaultMouseCursorChanged" );
const String System::EventMouseMoveScalingChanged( "MouseMoveScalingChanged" );
const String System::EventDisplaySizeChanged( "DisplaySizeChanged" );
const String System::EventRenderedStringParserChanged("RenderedStringParserChanged");

/*************************************************************************
    Constructor
*************************************************************************/
System::System(Renderer& renderer,               
               const String& configFile,
               const String& logFile)

: d_renderer(&renderer),  
  d_defaultFont(0),
  d_wndWithMouse(0),
  d_activeSheet(0),
  d_modalTarget(0),
  d_sysKeys(0),
  d_lshift(false),
  d_rshift(false),
  d_lctrl(false),
  d_rctrl(false),
  d_lalt(false),
  d_ralt(false),
  d_click_timeout(DefaultSingleClickTimeout),
  d_dblclick_timeout(DefaultMultiClickTimeout),
  d_dblclick_size(DefaultMultiClickAreaSize),
  d_clickTrackerPimpl(new MouseClickTrackerImpl),
  d_defaultMouseCursor(0),  
  d_mouseScalingFactor(1.0f), 
  //d_defaultTooltip(0),
  d_weOwnTooltip(false),  
  d_generateMouseClickEvents(true)
{
    // Start out by fixing the numeric locale to C (we depend on this behaviour)
    // consider a UVector2 as a property {{0.5,0},{0.5,0}} could become {{0,5,0},{0,5,0}}
    setlocale(LC_NUMERIC, "C");

	// create the core system singleton objects
	createSingletons();

    hgeLogger& logger(hgeLogger::getSingleton());

    outputLogHeader();

    // beginning main init
    logger.logEvent("---- Begining CEGUI System initialisation ----");

    // add the window factories for the core window types
    addStandardWindowFactories();

    // GUISheet's name was changed, register an alias so both can be used
    //WindowFactoryManager::getSingleton().addWindowTypeAlias("DefaultGUISheet", GUISheet::WidgetTypeName);

    char addr_buff[32];
    sprintf(addr_buff, "(%p)", static_cast<void*>(this));
    logger.logEvent("System singleton created. " + String(addr_buff));
    logger.logEvent("---- CEGUI System initialisation completed ----");
    logger.logEvent("");

    //// set up defaults
    //config.initialiseDefaultFont();
    //config.initialiseDefaultMouseCursor();
    //config.initialiseDefaulTooltip();
    //config.initialiseDefaultGUISheet();
}


/*************************************************************************
	Destructor
*************************************************************************/
System::~System(void)
{
	hgeLogger::getSingleton().logEvent("---- Begining CEGUI System destruction ----");

    //
	// perform cleanup in correct sequence
	//
    // ensure no windows get created during destruction.  NB: I'm allowing the
    // potential exception to escape here so as to make it obvious that client
    // code should really be adjusted to not create windows during cleanup.

	// destroy windows so it's safe to destroy factories
    WindowManager::getSingleton().destroyAllWindows();
    WindowManager::getSingleton().cleanDeadPool();

    // remove factories so it's safe to unload GUI modules
	WindowFactoryManager::getSingleton().removeAllFactories();

	// cleanup singletons
    destroySingletons();

    char addr_buff[32];
    sprintf(addr_buff, "(%p)", static_cast<void*>(this));
	hgeLogger::getSingleton().logEvent("System singleton destroyed. " +
       String(addr_buff));
	hgeLogger::getSingleton().logEvent("---- CEGUI System destruction completed ----");

	delete d_clickTrackerPimpl;
}


/*************************************************************************
	Render the GUI for this frame
*************************************************************************/
void System::renderGUI(void)
{
    d_renderer->beginRendering();

	if (d_gui_redraw)
	{
		if (d_activeSheet)
		{
            RenderingSurface& rs = d_activeSheet->getTargetRenderingSurface();
            rs.clearGeometry();

            //if (rs.isRenderingWindow())
            //    static_cast<RenderingWindow&>(rs).getOwner().clearGeometry();

			d_activeSheet->render();
		}
        // no sheet, so ensure default surface geometry is cleared
        else
            d_renderer->getDefaultRenderingRoot().clearGeometry();

		d_gui_redraw = false;
	}

    d_renderer->getDefaultRenderingRoot().draw();
	//MouseCursor::getSingleton().draw();
    d_renderer->endRendering();

    // do final destruction on dead-pool windows
    WindowManager::getSingleton().cleanDeadPool();
}


/*************************************************************************
	Set the active GUI sheet (root) window.
*************************************************************************/
Window* System::setGUISheet(Window* sheet)
{
	Window* old = d_activeSheet;
	d_activeSheet = sheet;

    // Force and update for the area Rects for 'sheet' so they're correct according
    // to the screen size.
    if (sheet)
    {
        WindowEventArgs sheetargs(0);
        sheet->onParentSized(sheetargs);
    }

	// fire event
	WindowEventArgs args(old);
	onGUISheetChanged(args);

	return old;
}


/*************************************************************************
	Set the default font to be used by the system
*************************************************************************/
void System::setDefaultFont(const String& name)
{
	if (name.empty())
	{
		setDefaultFont(0);
	}
	else
	{
		setDefaultFont(&FontManager::getSingleton().getFont(name));
	}

}

/*************************************************************************
	Set the default font to be used by the system
*************************************************************************/
void System::setDefaultFont(hgeFont* font)
{
	d_defaultFont = font;

	// fire event
	EventArgs args;
	onDefaultFontChanged(args);
}

HCURSOR System::getDefaultMouseCursor(void) const
{
	return d_defaultMouseCursor;
}

/*************************************************************************
    Set the image to be used as the default mouse cursor.
*************************************************************************/
void System::setDefaultMouseCursor(const String& cursorFile)
{
	HCURSOR hCursor = MouseCursor::getSingleton().getCursor(cursorFile);

	// the default, default, is for nothing!
	if (d_defaultMouseCursor == hCursor)
		return;

    // update our pointer for the default mouse cursor image.
    d_defaultMouseCursor = hCursor;

    // fire off event.
    EventArgs args;
    onDefaultMouseCursorChanged(args);
}

/*************************************************************************
	return the current mouse movement scaling factor.
*************************************************************************/
float System::getMouseMoveScaling(void) const
{
	return d_mouseScalingFactor;
}


/*************************************************************************
	Set the current mouse movement scaling factor
*************************************************************************/
void System::setMouseMoveScaling(float scaling)
{
	d_mouseScalingFactor = scaling;

	// fire off event.
	EventArgs args;
	onMouseMoveScalingChanged(args);
}


/*************************************************************************
	Method that injects a mouse movement event into the system
*************************************************************************/
bool System::injectMouseMove(float delta_x, float delta_y)
{
    MouseEventArgs ma(0);
    ma.moveDelta.d_x = delta_x * d_mouseScalingFactor;
    ma.moveDelta.d_y = delta_y * d_mouseScalingFactor;

    // no movement means no event
    if ((ma.moveDelta.d_x == 0) && (ma.moveDelta.d_y == 0))
        return false;

    ma.sysKeys = d_sysKeys;
    ma.wheelChange = 0;
    ma.clickCount = 0;
    ma.button = NoButton;

    // move the mouse cursor & update position in args.
    MouseCursor& mouse(MouseCursor::getSingleton());
    mouse.offsetPosition(ma.moveDelta);
    ma.position = mouse.getPosition();

    return mouseMoveInjection_impl(ma);
}

/*************************************************************************
	Method that injects that the mouse is leaves the application window
*************************************************************************/
bool System::injectMouseLeaves(void)
{
	MouseEventArgs ma(0);

	// if there is no window that currently contains the mouse, then
	// there is nowhere to send input
	if (d_wndWithMouse)
	{
		ma.position = d_wndWithMouse->
          getUnprojectedPosition(MouseCursor::getSingleton().getPosition());
		ma.moveDelta = Point(0.0f, 0.0f);
		ma.button = NoButton;
		ma.sysKeys = d_sysKeys;
		ma.wheelChange = 0;
		ma.window = d_wndWithMouse;
		ma.clickCount = 0;

		d_wndWithMouse->onMouseLeaves(ma);
		d_wndWithMouse = 0;
	}

	return ma.handled != 0;
}


/*************************************************************************
	Method that injects a mouse button down event into the system.
*************************************************************************/
bool System::injectMouseButtonDown(MouseButton button)
{
    // update system keys
    d_sysKeys |= mouseButtonToSyskey(button);

    MouseEventArgs ma(0);
    ma.position = MouseCursor::getSingleton().getPosition();
    ma.moveDelta = Vector2(0.0f, 0.0f);
    ma.button = button;
    ma.sysKeys = d_sysKeys;
    ma.wheelChange = 0;
    ma.window = getTargetWindow(ma.position, false);
    // make mouse position sane for this target window
    if (ma.window)
        ma.position = ma.window->getUnprojectedPosition(ma.position);

    //
    // Handling for multi-click generation
    //
    MouseClickTracker& tkr = d_clickTrackerPimpl->click_trackers[button];

    tkr.d_click_count++;

    // if multi-click requirements are not met
    if (((d_dblclick_timeout > 0) && (tkr.d_timer.elapsed() > d_dblclick_timeout)) ||
        (!tkr.d_click_area.isPointInRect(ma.position)) ||
        (tkr.d_target_window != ma.window) ||
        (tkr.d_click_count > 3))
    {
        // reset to single down event.
        tkr.d_click_count = 1;

        // build new allowable area for multi-clicks
        tkr.d_click_area.setPosition(ma.position);
        tkr.d_click_area.setSize(d_dblclick_size);
        tkr.d_click_area.offset(Point(-(d_dblclick_size.d_x / 2), -(d_dblclick_size.d_y / 2)));

        // set target window for click events on this tracker
        tkr.d_target_window = ma.window;
    }

    // set click count in the event args
    ma.clickCount = tkr.d_click_count;

    if (ma.window)
    {
        if (d_generateMouseClickEvents && ma.window->wantsMultiClickEvents())
        {
            switch (tkr.d_click_count)
            {
            case 1:
                ma.window->onMouseButtonDown(ma);
                break;

            case 2:
                ma.window->onMouseDoubleClicked(ma);
                break;

            case 3:
                ma.window->onMouseTripleClicked(ma);
                break;
            }
        }
        // click generation disabled, or current target window does not want
        // multi-clicks, so just send a mouse down event instead.
        else
        {
            ma.window->onMouseButtonDown(ma);
        }
    }

    // reset timer for this tracker.
    tkr.d_timer.restart();

    return ma.handled != 0;
}


/*************************************************************************
	Method that injects a mouse button up event into the system.
*************************************************************************/
bool System::injectMouseButtonUp(MouseButton button)
{
    // update system keys
    d_sysKeys &= ~mouseButtonToSyskey(button);

    MouseEventArgs ma(0);
    ma.position = MouseCursor::getSingleton().getPosition();
	ma.moveDelta = Point(0.0f, 0.0f);
    ma.button = button;
    ma.sysKeys = d_sysKeys;
    ma.wheelChange = 0;
    ma.window = getTargetWindow(ma.position, false);
    // make mouse position sane for this target window
    if (ma.window)
        ma.position = ma.window->getUnprojectedPosition(ma.position);

    // get the tracker that holds the number of down events seen so far for this button
    MouseClickTracker& tkr = d_clickTrackerPimpl->click_trackers[button];
    // set click count in the event args
    ma.clickCount = tkr.d_click_count;

    // if there is no window, inputs can not be handled.
    if (!ma.window)
        return false;

    // send 'up' input to the window
    ma.window->onMouseButtonUp(ma);
    // store whether the 'up' part was handled so we may reuse the EventArgs
    const UINT upHandled = ma.handled;

    // send MouseClicked event if the requirements for that were met
    if (d_generateMouseClickEvents &&
        ((d_click_timeout == 0) || (tkr.d_timer.elapsed() <= d_click_timeout)) &&
        (tkr.d_click_area.isPointInRect(ma.position)) &&
        (tkr.d_target_window == ma.window))
    {
        ma.handled = 0;
        ma.window->onMouseClicked(ma);
    }

    return (ma.handled + upHandled) != 0;
}


/*************************************************************************
	Method that injects a key down event into the system.
*************************************************************************/
bool System::injectKeyDown(UINT key_code)
{
    // update system keys
    d_sysKeys |= keyCodeToSyskey((Key::Scan)key_code, true);

    KeyEventArgs args(getKeyboardTargetWindow());

    // if there's no destination window, input can't be handled.
    if (!args.window)
        return false;

    args.scancode = (Key::Scan)key_code;
    args.sysKeys = d_sysKeys;

    args.window->onKeyDown(args);
    return args.handled != 0;
}


/*************************************************************************
	Method that injects a key up event into the system.
*************************************************************************/
bool System::injectKeyUp(UINT key_code)
{
    // update system keys
    d_sysKeys &= ~keyCodeToSyskey((Key::Scan)key_code, false);

    KeyEventArgs args(getKeyboardTargetWindow());

    // if there's no destination window, input can't be handled.
    if (!args.window)
        return false;

    args.scancode = (Key::Scan)key_code;
    args.sysKeys = d_sysKeys;

    args.window->onKeyUp(args);
    return args.handled != 0;
}


/*************************************************************************
	Method that injects a typed character event into the system.
*************************************************************************/
bool System::injectChar(utf32 code_point)
{
    KeyEventArgs args(getKeyboardTargetWindow());

    // if there's no destination window, input can't be handled.
    if (!args.window)
        return false;

    args.codepoint = code_point;
    args.sysKeys = d_sysKeys;

    args.window->onCharacter(args);
    return args.handled != 0;
}


/*************************************************************************
	Method that injects a mouse-wheel / scroll-wheel event into the system.
*************************************************************************/
bool System::injectMouseWheelChange(float delta)
{
    MouseEventArgs ma(0);
    ma.position = MouseCursor::getSingleton().getPosition();
    ma.moveDelta = Vector2(0.0f, 0.0f);
    ma.button = NoButton;
    ma.sysKeys = d_sysKeys;
    ma.wheelChange = delta;
    ma.clickCount = 0;
    ma.window = getTargetWindow(ma.position, false);
    // make mouse position sane for this target window
    if (ma.window)
        ma.position = ma.window->getUnprojectedPosition(ma.position);

    // if there is no target window, input can not be handled.
    if (!ma.window)
        return false;

    ma.window->onMouseWheel(ma);
    return ma.handled != 0;
}


/*************************************************************************
	Method that injects a new position for the mouse cursor.
*************************************************************************/
bool System::injectMousePosition(float x_pos, float y_pos)
{
    const Point new_position(x_pos, y_pos);
    MouseCursor& mouse(MouseCursor::getSingleton());

    // setup mouse movement event args object.
    MouseEventArgs ma(0);
    ma.moveDelta = new_position - mouse.getPosition();

    // no movement means no event
    if ((ma.moveDelta.d_x == 0) && (ma.moveDelta.d_y == 0))
        return false;

    ma.sysKeys = d_sysKeys;
    ma.wheelChange = 0;
    ma.clickCount = 0;
    ma.button = NoButton;

    // move mouse cursor to new position
    mouse.setPosition(new_position);
    // update position in args (since actual position may be constrained)
    ma.position = mouse.getPosition();

    return mouseMoveInjection_impl(ma);
}


/*************************************************************************
	Method to inject time pulses into the system.
*************************************************************************/
bool System::injectTimePulse(float timeElapsed)
{
    // if no visible active sheet, input can't be handled
    if (!d_activeSheet || !d_activeSheet->isVisible())
        return false;

    // else pass to sheet for distribution.
    d_activeSheet->update(timeElapsed);
    // this input is then /always/ considered handled.
    return true;
}


/*************************************************************************
	Return window that should get mouse inouts when mouse it at 'pt'
*************************************************************************/
Window* System::getTargetWindow(const Point& pt,
                                const bool allow_disabled) const
{
    // if there is no GUI sheet visible, then there is nowhere to send input
    if (!d_activeSheet || !d_activeSheet->isVisible())
        return 0;

    Window* dest_window = Window::getCaptureWindow();

    if (!dest_window)
    {
        dest_window = d_activeSheet->
            getTargetChildAtPosition(pt, allow_disabled);

        if (!dest_window)
            dest_window = d_activeSheet;
    }
    else
    {
        if (dest_window->distributesCapturedInputs())
        {
            Window* child_window = dest_window->
                getTargetChildAtPosition(pt, allow_disabled);

            if (child_window)
                dest_window = child_window;
        }
    }

    // modal target overrules
    if (d_modalTarget && dest_window != d_modalTarget)
        if (!dest_window->isAncestor(d_modalTarget))
            dest_window = d_modalTarget;

    return dest_window;
}


/*************************************************************************
	Return window that should receive keyboard input
*************************************************************************/
Window* System::getKeyboardTargetWindow(void) const
{
    // if no active sheet, there is no target widow.
    if (!d_activeSheet || !d_activeSheet->isVisible())
        return 0;

    // handle normal non-modal situations
    if (!d_modalTarget)
        return d_activeSheet->getActiveChild();

    // handle possible modal window.
    Window* const target = d_modalTarget->getActiveChild();
    return target ? target : d_modalTarget;
}


/*************************************************************************
	Return the next window that should receive input in the chain
*************************************************************************/
Window* System::getNextTargetWindow(Window* w) const
{
	// if we have not reached the modal target, return the parent
	if (w != d_modalTarget)
	{
		return w->getParent();
	}

	// otherwise stop now
	return 0;
}


/*************************************************************************
	Translate a MouseButton value into the corresponding SystemKey value
*************************************************************************/
SystemKey System::mouseButtonToSyskey(MouseButton btn) const
{
	switch (btn)
	{
	case LeftButton:
		return LeftMouse;

	case RightButton:
		return RightMouse;

	case MiddleButton:
		return MiddleMouse;

	case X1Button:
		return X1Mouse;

	case X2Button:
		return X2Mouse;

	default:
		throw InvalidRequestException("System::mouseButtonToSyskey - the parameter 'btn' is not a valid MouseButton value.");
	}
}


/*************************************************************************
	Translate a Key::Scan value into the corresponding SystemKey value
*************************************************************************/
SystemKey System::keyCodeToSyskey(Key::Scan key, bool direction)
{
	switch (key)
	{
	case Key::LeftShift:
		d_lshift = direction;

		if (!d_rshift)
		{
			return Shift;
		}
		break;

	case Key::RightShift:
		d_rshift = direction;

		if (!d_lshift)
		{
			return Shift;
		}
		break;


	case Key::LeftControl:
		d_lctrl = direction;

		if (!d_rctrl)
		{
			return Control;
		}
		break;

	case Key::RightControl:
		d_rctrl = direction;

		if (!d_lctrl)
		{
			return Control;
		}
		break;

	case Key::LeftAlt:
		d_lalt = direction;

		if (!d_ralt)
		{
			return Alt;
		}
		break;

	case Key::RightAlt:
		d_ralt = direction;

		if (!d_lalt)
		{
			return Alt;
		}
		break;

    default:
        break;
	}

	// if not a system key or overall state unchanged, return 0.
	return (SystemKey)0;
}


System&	System::getSingleton(void)
{
	return Singleton<System>::getSingleton();
}


System*	System::getSingletonPtr(void)
{
	return Singleton<System>::getSingletonPtr();
}



/*************************************************************************
	Set the timeout to be used for the generation of single-click events.
*************************************************************************/
void System::setSingleClickTimeout(double timeout)
{
	d_click_timeout = timeout;

	// fire off event.
	EventArgs args;
	onSingleClickTimeoutChanged(args);
}


/*************************************************************************
	Set the timeout to be used for the generation of multi-click events.
*************************************************************************/
void System::setMultiClickTimeout(double timeout)
{
	d_dblclick_timeout = timeout;

	// fire off event.
	EventArgs args;
	onMultiClickTimeoutChanged(args);
}


/*************************************************************************
	Set the size of the allowable mouse movement tolerance used when
	generating multi-click events.
*************************************************************************/
void System::setMultiClickToleranceAreaSize(const Size&	sz)
{
	d_dblclick_size = sz;

	// fire off event.
	EventArgs args;
	onMultiClickAreaSizeChanged(args);
}


/*************************************************************************
	Handler called when the main system GUI Sheet (or root window) is changed
*************************************************************************/
void System::onGUISheetChanged(WindowEventArgs& e)
{
	fireEvent(EventGUISheetChanged, e, EventNamespace);
}


/*************************************************************************
	Handler called when the single-click timeout value is changed.
*************************************************************************/
void System::onSingleClickTimeoutChanged(EventArgs& e)
{
	fireEvent(EventSingleClickTimeoutChanged, e, EventNamespace);
}


/*************************************************************************
	Handler called when the multi-click timeout value is changed.
*************************************************************************/
void System::onMultiClickTimeoutChanged(EventArgs& e)
{
	fireEvent(EventMultiClickTimeoutChanged, e, EventNamespace);
}


/*************************************************************************
	Handler called when the size of the multi-click tolerance area is
	changed.
*************************************************************************/
void System::onMultiClickAreaSizeChanged(EventArgs& e)
{
	fireEvent(EventMultiClickAreaSizeChanged, e, EventNamespace);
}


/*************************************************************************
	Handler called when the default system font is changed.
*************************************************************************/
void System::onDefaultFontChanged(EventArgs& e)
{
    // here we need to inform every window using the default font that
    // it's font has been changed.
    WindowManager::WindowIterator iter =
        WindowManager::getSingleton().getIterator();

    // Args structure we will re-use for all windows.
    WindowEventArgs args(0);

    while (!iter.isAtEnd())
    {
        Window* wnd = iter.getCurrentValue();

        if (wnd->getFont(false) == 0)
        {
            args.window = wnd;
            wnd->onFontChanged(args);
            // ensure 'handled' state is reset.
            args.handled = 0;
        }

        ++iter;
    }

	fireEvent(EventDefaultFontChanged, e, EventNamespace);
}


/*************************************************************************
	Handler called when the default system mouse cursor image is changed.
*************************************************************************/
void System::onDefaultMouseCursorChanged(EventArgs& e)
{
	fireEvent(EventDefaultMouseCursorChanged, e, EventNamespace);
}


/*************************************************************************
	Handler called when the mouse movement scaling factor is changed.
*************************************************************************/
void System::onMouseMoveScalingChanged(EventArgs& e)
{
	fireEvent(EventMouseMoveScalingChanged, e, EventNamespace);
}


/*************************************************************************
	Handler method for display size change notifications
*************************************************************************/
void System::notifyDisplaySizeChanged(const Size& new_size)
{
    // notify other components of the display size change
    d_renderer->setDisplaySize(new_size);
    //ImagesetManager::getSingleton().notifyDisplaySizeChanged(new_size);
    //FontManager::getSingleton().notifyDisplaySizeChanged(new_size);
    MouseCursor::getSingleton().notifyDisplaySizeChanged(new_size);

	// notify gui sheet / root if size change, event propagation will ensure everything else
	// gets updated as required.
    //
    // FIXME: This is no longer correct, the RenderTarget the sheet is using as
    // FIXME: it's parent element may not be the main screen.
	if (d_activeSheet)
	{
		WindowEventArgs args(0);
		d_activeSheet->onParentSized(args);

        // regardless of what is done above, invalidate all windows.  This is
        // required since geometry can be wrong with referenced textures no
        // longer existing due to auto-scaling effect (mainly affects fonts).
        d_activeSheet->invalidate(true);
	}

    // Fire event
    DisplayEventArgs args(new_size);
    fireEvent(EventDisplaySizeChanged, args, EventNamespace);

    hgeLogger::getSingleton().logEvent(
        "Display resize:");
        //" w=" + PropertyHelper::floatToString(new_size.d_x) +
        //" h=" + PropertyHelper::floatToString(new_size.d_y));
}

/*************************************************************************
	Internal method used to inform the System object whenever a window is
	destroyed, so that System can perform any required housekeeping.
*************************************************************************/
void System::notifyWindowDestroyed(const Window* window)
{
	if (d_wndWithMouse == window)
	{
		d_wndWithMouse = 0;
	}

	if (d_activeSheet == window)
	{
		d_activeSheet = 0;
	}

	if (d_modalTarget == window)
	{
		d_modalTarget = 0;
	}

}

//void System::setDefaultTooltip(Tooltip* tooltip)
//{
//    // destroy current custom tooltip if one exists and we created it
//    if (d_defaultTooltip && d_weOwnTooltip)
//        WindowManager::getSingleton().destroyWindow(d_defaultTooltip);
//
//    // set new custom tooltip
//    d_weOwnTooltip = false;
//    d_defaultTooltip = tooltip;
//
//    if (d_defaultTooltip)
//        d_defaultTooltip->setWritingXMLAllowed(false);
//}
//
//void System::setDefaultTooltip(const String& tooltipType)
//{
//    // destroy current tooltip if one exists and we created it
//    if (d_defaultTooltip && d_weOwnTooltip)
//        WindowManager::getSingleton().destroyWindow(d_defaultTooltip);
//
//    if (tooltipType.empty())
//    {
//        d_defaultTooltip = 0;
//        d_weOwnTooltip = false;
//    }
//    else
//    {
//        try
//        {
//            d_defaultTooltip = static_cast<Tooltip*>(WindowManager::getSingleton().createWindow(tooltipType, "System::default__auto_tooltip__"));
//            d_weOwnTooltip = true;
//            d_defaultTooltip->setWritingXMLAllowed(false);
//        }
//        catch(UnknownObjectException x)
//        {
//            d_defaultTooltip = 0;
//            d_weOwnTooltip = false;
//        }
//    }
//}

void System::outputLogHeader()
{
    hgeLogger& l(hgeLogger::getSingleton());
    l.logEvent("");
    l.logEvent("********************************************************************************");
    l.logEvent("* Important:                                                                   *");
    l.logEvent("*     To get support at the CEGUI forums, you must post _at least_ the section *");
    l.logEvent("*     of this log file indicated below.  Failure to do this will result in no  *");
    l.logEvent("*     support being given; please do not waste our time.                       *");
    l.logEvent("********************************************************************************");
    l.logEvent("********************************************************************************");
    l.logEvent("* -------- START OF ESSENTIAL SECTION TO BE POSTED ON THE FORUM       -------- *");
    l.logEvent("********************************************************************************");
    l.logEvent("---- Version " + d_strVersion + " ----");
    l.logEvent("---- Renderer module is: " + d_renderer->getIdentifierString() + " ----");
    l.logEvent("********************************************************************************");
    l.logEvent("* -------- END OF ESSENTIAL SECTION TO BE POSTED ON THE FORUM         -------- *");
    l.logEvent("********************************************************************************");
    l.logEvent("");
}

void System::addStandardWindowFactories()
{
    // Add factories for types all base elements
	WindowFactoryManager::addFactory< TplWindowFactory<GUISheet> >();
   /* 
    WindowFactoryManager::addFactory< WindowFactory<DragContainer> >();
    WindowFactoryManager::addFactory< WindowFactory<ScrolledContainer> >();
    WindowFactoryManager::addFactory< WindowFactory<ClippedContainer> >();
    WindowFactoryManager::addFactory< WindowFactory<Checkbox> >();
    WindowFactoryManager::addFactory< WindowFactory<PushButton> >();
    WindowFactoryManager::addFactory< WindowFactory<RadioButton> >();
    WindowFactoryManager::addFactory< WindowFactory<Combobox> >();
    WindowFactoryManager::addFactory< WindowFactory<ComboDropList> >();
    WindowFactoryManager::addFactory< WindowFactory<Editbox> >();
    WindowFactoryManager::addFactory< WindowFactory<FrameWindow> >();
    WindowFactoryManager::addFactory< WindowFactory<ItemEntry> >();
    WindowFactoryManager::addFactory< WindowFactory<Listbox> >();
    WindowFactoryManager::addFactory< WindowFactory<ListHeader> >();
    WindowFactoryManager::addFactory< WindowFactory<ListHeaderSegment> >();
    WindowFactoryManager::addFactory< WindowFactory<Menubar> >();
    WindowFactoryManager::addFactory< WindowFactory<PopupMenu> >();
    WindowFactoryManager::addFactory< WindowFactory<MenuItem> >();
    WindowFactoryManager::addFactory< WindowFactory<MultiColumnList> >();
    WindowFactoryManager::addFactory< WindowFactory<MultiLineEditbox> >();
    WindowFactoryManager::addFactory< WindowFactory<ProgressBar> >();
    WindowFactoryManager::addFactory< WindowFactory<ScrollablePane> >();
    WindowFactoryManager::addFactory< WindowFactory<Scrollbar> >();
    WindowFactoryManager::addFactory< WindowFactory<Slider> >();
    WindowFactoryManager::addFactory< WindowFactory<Spinner> >();
    WindowFactoryManager::addFactory< WindowFactory<TabButton> >();
    WindowFactoryManager::addFactory< WindowFactory<TabControl> >();
    WindowFactoryManager::addFactory< WindowFactory<Thumb> >();
    WindowFactoryManager::addFactory< WindowFactory<Titlebar> >();
    WindowFactoryManager::addFactory< WindowFactory<Tooltip> >();
    WindowFactoryManager::addFactory< WindowFactory<ItemListbox> >();
    WindowFactoryManager::addFactory< WindowFactory<GroupBox> >();
    WindowFactoryManager::addFactory< WindowFactory<Tree> >();*/
}

void System::createSingletons()
{
    // cause creation of other singleton objects
	new hgeLogger();
    new ImageManager();
    new FontManager();
    new WindowFactoryManager();
    new WindowManager();
    new MouseCursor();
    new GlobalEventSet();
    new WindowRendererManager();
}

void System::destroySingletons()
{
    delete  WindowManager::getSingletonPtr();
    delete  WindowFactoryManager::getSingletonPtr();
	delete  WindowRendererManager::getSingletonPtr();
    delete  FontManager::getSingletonPtr();
    delete  MouseCursor::getSingletonPtr();
    delete  ImageManager::getSingletonPtr();
    delete  GlobalEventSet::getSingletonPtr();
}


//----------------------------------------------------------------------------//
bool System::mouseMoveInjection_impl(MouseEventArgs& ma)
{
    updateWindowContainingMouse();

    // input can't be handled if there is no window to handle it.
    if (!d_wndWithMouse)
        return false;

    // make mouse position sane for this target window
    ma.position = d_wndWithMouse->getUnprojectedPosition(ma.position);
    // inform window about the input.
    ma.window = d_wndWithMouse;
    ma.handled = 0;
    ma.window->onMouseMove(ma);

    // return whether window handled the input.
    return ma.handled != 0;
}

//----------------------------------------------------------------------------//
bool System::updateWindowContainingMouse()
{
    MouseEventArgs ma(0);
    const Vector2 mouse_pos(MouseCursor::getSingleton().getPosition());

    Window* const curr_wnd_with_mouse = getTargetWindow(mouse_pos, true);

    // exit if window containing mouse has not changed.
    if (curr_wnd_with_mouse == d_wndWithMouse)
        return false;

    ma.sysKeys = d_sysKeys;
    ma.wheelChange = 0;
    ma.clickCount = 0;
    ma.button = NoButton;

    Window* oldWindow = d_wndWithMouse;
    d_wndWithMouse = curr_wnd_with_mouse;

    // inform previous window the mouse has left it
    if (oldWindow)
    {
        ma.window = oldWindow;
        ma.position = oldWindow->getUnprojectedPosition(mouse_pos);
        oldWindow->onMouseLeaves(ma);
    }

    // inform window containing mouse that mouse has entered it
    if (d_wndWithMouse)
    {
        ma.handled = 0;
        ma.window = d_wndWithMouse;
        ma.position = d_wndWithMouse->getUnprojectedPosition(mouse_pos);
        d_wndWithMouse->onMouseEnters(ma);
    }

    return true;
}

//----------------------------------------------------------------------------//
System& System::create(Renderer& renderer, const String& configFile,
                       const String& logFile)
{
    return *new System(renderer, configFile, logFile);
}

//----------------------------------------------------------------------------//
void System::destroy()
{
    delete System::getSingletonPtr();
}

//----------------------------------------------------------------------------//
RenderedStringParser* System::getDefaultCustomRenderedStringParser() const
{
    return d_customRenderedStringParser;
}

//----------------------------------------------------------------------------//
void System::setDefaultCustomRenderedStringParser(RenderedStringParser* parser)
{
    if (parser != d_customRenderedStringParser)
    {
        d_customRenderedStringParser = parser;
        
        // fire event
        EventArgs args;
        fireEvent(EventRenderedStringParserChanged, args, EventNamespace);
    }
}

//----------------------------------------------------------------------------//
bool System::isMouseClickEventGenerationEnabled() const
{
    return d_generateMouseClickEvents;
}

//----------------------------------------------------------------------------//
void System::setMouseClickEventGenerationEnabled(const bool enable)
{
    d_generateMouseClickEvents = enable;
}

//----------------------------------------------------------------------------//
bool System::injectMouseButtonClick(const MouseButton button)
{
    MouseEventArgs ma(0);
    ma.position = MouseCursor::getSingleton().getPosition();
    ma.window = getTargetWindow(ma.position, false);

    if (ma.window)
    {
        // initialise remainder of args struct.
        ma.moveDelta = Vector2(0.0f, 0.0f);
        ma.button = button;
        ma.sysKeys = d_sysKeys;
        ma.wheelChange = 0;
        // make mouse position sane for this target window
        ma.position = ma.window->getUnprojectedPosition(ma.position);
        // tell the window about the event.
        ma.window->onMouseClicked(ma);
    }

    return ma.handled != 0;
}

//----------------------------------------------------------------------------//
bool System::injectMouseButtonDoubleClick(const MouseButton button)
{
    MouseEventArgs ma(0);
    ma.position = MouseCursor::getSingleton().getPosition();
    ma.window = getTargetWindow(ma.position, false);

    if (ma.window && ma.window->wantsMultiClickEvents())
    {
        // initialise remainder of args struct.
        ma.moveDelta = Vector2(0.0f, 0.0f);
        ma.button = button;
        ma.sysKeys = d_sysKeys;
        ma.wheelChange = 0;
        // make mouse position sane for this target window
        ma.position = ma.window->getUnprojectedPosition(ma.position);
        // tell the window about the event.
        ma.window->onMouseDoubleClicked(ma);
    }

    return ma.handled != 0;
}

//----------------------------------------------------------------------------//
bool System::injectMouseButtonTripleClick(const MouseButton button)
{
    MouseEventArgs ma(0);
    ma.position = MouseCursor::getSingleton().getPosition();
    ma.window = getTargetWindow(ma.position, false);

    if (ma.window && ma.window->wantsMultiClickEvents())
    {
        // initialise remainder of args struct.
        ma.moveDelta = Vector2(0.0f, 0.0f);
        ma.button = button;
        ma.sysKeys = d_sysKeys;
        ma.wheelChange = 0;
        // make mouse position sane for this target window
        ma.position = ma.window->getUnprojectedPosition(ma.position);
        // tell the window about the event.
        ma.window->onMouseTripleClicked(ma);
    }

    return ma.handled != 0;
}
